#!/usr/bin/python


from python_interface_to_soprano import *

logger = logging.getLogger("Nao")



"""

This class offers the possibilities from the NAO Ontology: tags, description, rating.

"""


class Nao:
	
	def __init__(self, interface_to_soprano):
		"""
			:Parameters:
				`interface_to_soprano` : Interface
					An interface to call the methods : Interface
		"""
		
		self.i = interface_to_soprano

############################################################
	
	def addTag(self, document_uri, tag_label, tag_description="", tag_label_language=XMLSCHEMA_STRING):
		""" Add a tag in the database.
			:Parameters:
				`document_uri` : str
					The pimo:Document to be tagged.
				`tag_label` : str
					The string value to be used as a tag for document_uri.
				`tag_description` : str
					An optional description for that tag.
				`tag_label_language` : str
					An optional language for the tag to be stored. Will very often, if not always, be xmlschema:String (default value).
			:Returns: 0
			:Raises `CanNotException`: if the tag is already used to tag the given document.
		"""
		
		self.i.documentChecking(document_uri)
		
		# if the given document_uri already has a tag labeled tag_label, we will not do anything
		query = "SELECT * WHERE { <" + document_uri + "> <" + NAO_HASTAG + "> ?tag_id . ?tag_id <" + NAO_PREFLABEL + "> \"" + tag_label + "\"^^<" + XMLSCHEMA_STRING + "> }"
		existing_tag_ids = self.i.executeQuery(query)
		
		if (existing_tag_ids.next()):
			existing_tag_ids.close()
			raise CanNotException("The resource \"" + document_uri + "\" already has a tag named \"" + tag_label + "\".")
		
		existing_tag_ids.close()
		
		# the following points have been written paying attention to the NEPOMUK Annotation Ontology (NAO) standards, which can be found at http://www.semanticdesktop.org/ontologies/2007/08/15/nao/#2.3._Tagging_as_Annotation on the 2008-06-09
		
		# 1. create a new tag id to identify the new nao:Tag
		tag_id = self.i.getUniqueId()
		
		# 2. write the information about that tag in the database: rdf:type, nao:created, nao:prefLabel, nao:description
		
		# 2.1. rdf:type
		#		[tag_id, rdf:type, nao:tag, ?]
		error_code = self.i.registerStatement(tag_id, RDF_TYPE, NAO_TAG)
		# print error_code
		
		# 2.2. nao:created
		#		[tag_id, nao:created, date, ?]
		# Here, the type for the date should be XMLSCHEMA_DATETIME. But when we try to save the statement with this type, the date seems to be converted and appears in an ugly and wrong way (e.g. '-4713-01-01TZ'). Currently we store it as a string.
		date = getDateTime()
		error_code = self.i.registerStatement(tag_id, NAO_CREATED, date, XMLSCHEMA_STRING)
		#print statement
		# print error_code
		
		# 2.3. nao:prefLabel
		#		[tag_id, nao:prefLabel, tag_label, ?]
		error_code = self.i.registerStatement(tag_id, NAO_PREFLABEL, tag_label, tag_label_language)
		# print error_code
		
		# 2.4. nao:description
		#		[tag_id, nao:description, tag_description, ?]
		error_code = self.i.registerStatement(tag_id, NAO_DESCRIPTION, tag_description, XMLSCHEMA_STRING)
		# print error_code
		
		# 3. double-link that nao:Tag with the corresponding resource (given document_uri)
		
		# 3.1. one way
		#		[document_uri, nao:hasTag, tag_id, ?]
		error_code = self.i.registerStatement(document_uri, NAO_HASTAG, tag_id)
		# print error_code
		
		# 3.2. and the other
		#		[tag_id, nao:isTagFor, document_uri, ?]
		error_code = self.i.registerStatement(tag_id, NAO_ISTAGFOR, document_uri)
		# print error_code
		
		return 0
		
############################################################
	
	def removeTag(self, document_uri, tag_label, tag_label_language=XMLSCHEMA_STRING):
		""" Removes a tag from the database.
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to untag.
				`tag_label` : str
					The string value of the tag to remove.
				`tag_label_language` : str
					The language of the tag.
			:Returns: 0
			:Raises `CanNotException`: if tag_label does not exist as a tag for document_uri.
			:Raises `DatabaseIncoherenceException`: if there are at least two tag_uris corresponding to tag_label tagging document_uri, which is not allowed.
		"""
		
		logger.debug("removeTag(%s, %s)" % (document_uri, tag_label))
		
		self.i.documentChecking(document_uri)
		
		# remove every information attached to that tag
		
		# 1. find the tag id
		query = "SELECT * WHERE { ?tag_id <" + NAO_PREFLABEL + "> \"" + tag_label + "\"^^<" + tag_label_language + "> . <" + document_uri + "> <" + NAO_HASTAG + "> ?tag_id }"
		possible_tag_ids = self.i.executeQuery(query)
		
		tag_id = None
		if (possible_tag_ids.next()):
			tag_id = possible_tag_ids.current()[0]["tag_id"][1]
		else:
			possible_tag_ids.close()
			raise CanNotException("Not any tag labeled \""+tag_label+"\" for document_uri \""+document_uri+"\".")
		
		# there should be only one result
		if (possible_tag_ids.next()): # there is more than one result!
			possible_tag_ids.close()
			raise DatabaseIncoherenceException("Database incoherence: there is more than one tag_id for the same tag of " + document_uri + "!")
		
		possible_tag_ids.close()
		
		logger.debug("tag_id = %s" % tag_id)
		
		# delete all the statements concerning this tag
		
		# 2.1. [tag_id, rdf:type, nao:tag, ?]
		#print "2.1"
		error_code = self.i.deleteStatement(tag_id, RDF_TYPE, NAO_TAG)
		# print error_code
		
		#print "2.3"
		# 2.3. [tag_id, nao:prefLabel, tag_label, ?]
		error_code = self.i.deleteStatement(tag_id, NAO_PREFLABEL, tag_label, tag_label_language)
		# print error_code
		
		#print "3.1"
		# 3.1. [document_uri, nao:hasTag, tag_id, ?]
		error_code = self.i.deleteStatement(document_uri, NAO_HASTAG, tag_id)
		# print error_code
		
		#print "3.2"
		# 3.2. [tag_id, nao:isTagFor, document_uri, ?]
		error_code = self.i.deleteStatement(tag_id, NAO_ISTAGFOR, document_uri)
		# print error_code
		
		#print "2.2"
		# 2.2. [tag_id, nao:created, date (unknown here), ?]
		error_code = self.i.deleteAllStatements(tag_id, NAO_CREATED, "?")
		# print error_code
		
		#print "2.4"
		# 2.4. [tag_id, nao:description, tag_description (unknown here), ?]
		error_code = self.i.deleteAllStatements(tag_id, NAO_DESCRIPTION, "?")
		# print error_code
		
		return 0
		
############################################################

	# Returns the description of the given tag (document_uri - tag label) or "no description"
	def getDescriptionOfTag(self, document_uri, tag_label, tag_label_language=XMLSCHEMA_STRING):
		""" Get the description of a tag.
			:Parameters:
				`document_uri` : str
					The tagged pimo:Document.
				`tag_label` : str
					The tag that we want the description of.
				`tag_label_language` : str
					The language of the tag.
			:Returns: The wanted description - str.
			:Raises `DatabaseIncoherenceException`: if there are at least two tag_uris corresponding to tag_label tagging document_uri, which is not allowed. Another condition for raising this exception is when the tag has not any description in the database (it should at least have the empty string "").
		"""
		
		self.i.documentChecking(document_uri)
		
		# 1. find the tag id
		query = "SELECT * WHERE { ?tag_id <" + NAO_PREFLABEL + "> \"" + tag_label + "\"^^<" + XMLSCHEMA_STRING + "> . <" + document_uri + "> <" + NAO_HASTAG + "> ?tag_id }"
		possible_tag_ids = self.i.executeQuery(query)
		
		tag_id = None
		if (possible_tag_ids.next()):
			tag_id = possible_tag_ids.current()[0]["tag_id"][1]
		else:
			return "no description"
		
		# there should be only one result
		if (possible_tag_ids.next()): # there is more than one result!
			possible_tag_ids.close()
			raise DatabaseIncoherenceException("Database incoherence: there is more than one tag_id for the same tag of " + document_uri + "!")
		
		possible_tag_ids.close()
		
		description = self.i.getImage(tag_id, NAO_DESCRIPTION)
		
		if description == None:
			raise DatabaseIncoherenceException("The tag %s has no associated description." % tag_id)
		else:
			return description

############################################################

	def getAllTagsSet(self):
		""" Get all the tags of all found pimo:Documents.
			:Returns: A set of tuples representing tags in this way: (page_uri, tag_label, page_title, tag_desc)
		"""
		
		query = "SELECT * WHERE { ?doc_uri <%s> ?tag_id . ?tag_id <%s> ?tag . ?tag_id <%s> ?desc . ?doc_uri <%s> ?page_uri . ?doc_uri <%s> ?title}" % (NAO_HASTAG, NAO_PREFLABEL, NAO_DESCRIPTION, PIMO_GROUNDINGOCCURRENCE, NAO_PREFLABEL)
		logger.debug(query)
		tags = self.i.executeQuery(query)
		
		all_tags = set()
		
		while tags.next():
			
			#tag_id = tags.current()[0]["tag_id"][1]
			tag = tags.current()[0]["tag"][1]
			doc_uri = tags.current()[0]["doc_uri"][1]
			page_uri = tags.current()[0]["page_uri"][1]
			title = tags.current()[0]["title"][1]
			desc = tags.current()[0]["desc"][1]
			#logger.debug("values = %s ; %s ; %s ; %s ; %s" % (tag, doc_uri, page_uri, title, desc))
			
			all_tags.add( (page_uri, tag, title, desc) )
			
		tags.close()
		
		return all_tags

############################################################

	def setDescription(self, document_uri, description):
		""" Sets the description of document_uri to description. A resource has only one description, so this erases any (possibly existing) previous value.
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to add the description to.
			:Returns: 0
		"""
		
		self.i.documentChecking(document_uri)
		
		# as described at http://www.semanticdesktop.org/ontologies/2007/08/15/nao/#2.2.10._nao:description (available on the 2008-06-08), "The maximum cardinality [of a nao:description] is 1".
		# so we first delete the (possibly) existing description before adding the new one.
		self.removeDescription(document_uri)
		
		# and then set the given description
		error_code = self.i.registerStatement(document_uri, NAO_DESCRIPTION, description, XMLSCHEMA_STRING)
		
		return 0
		
############################################################
	
	def removeDescription(self, document_uri):
		""" Removes the (possibly existing) description of document_uri.
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to remove the description of.
			:Returns: 0
		"""
		
		self.i.documentChecking(document_uri)
		
		error_code = self.i.deleteAllStatements(document_uri, NAO_DESCRIPTION, "?")
		
		return 0
		
############################################################

	def getDescriptionOf(self, document_uri):
		""" Returns the description of the given document_uri.
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to retrieve the description of.
			:Returns: the wanted description - str.
			:Raises `DatabaseIncoherenceException`: if there are more than one description for document_uri.
		"""
		
		self.i.documentChecking(document_uri)
		
		query = "SELECT ?desc WHERE { <" + document_uri + "> <" + NAO_DESCRIPTION + "> ?desc }"
		descriptions = self.i.executeQuery(query)
		
		description = None
		if descriptions.next():
			description = descriptions.current()[0]["desc"][1]
		else:
			description = "no description"
		
		if descriptions.next():
			descriptions.close()
			raise DatabaseIncoherenceException("Database incoherence: there are more than one description for \"" + document_uri + "\"")
		
		descriptions.close()
		
		return description
		
############################################################

	def setNumericRating(self, document_uri, value):
		""" Sets the numeric rating of the given document_uri to value. Currently, only integer numbers are allowed because of a bad conversion (?) when passing float numbers to be registered as XMLSCHEMA_DECIMAL (same problem as with XMLSCHEMA_DATETIME).
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to the set the rating of.
				`value` : str.
					The value passed as a string but representing a number.
				:Returns: 0
		"""
		
		self.i.documentChecking(document_uri)
		
		# as described at http://www.semanticdesktop.org/ontologies/2007/08/15/nao/#numericRating (available on the 2008-06-11), nao:numericRating has "Maximal cardinality [=] 1".
		# so we first delete the (possibly) existing numericRating before adding the new one.
		self.removeNumericRating(document_uri)
		
		# and then set the rating to the given value
		#error_code = self.i.registerStatement(document_uri, NAO_NUMERICRATING, value, XMLSCHEMA_DECIMAL)
		error_code = self.i.registerStatement(document_uri, NAO_NUMERICRATING, value, XMLSCHEMA_INT)
		
		return 0
	
############################################################

	# Removes the numeric rating for this document_uri.
	def removeNumericRating(self, document_uri):
		""" Removes the rating of document_uri.
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to the remove the rating of.
			:Returns: 0
		"""
		
		self.i.documentChecking(document_uri)
		
		error_code = self.i.deleteAllStatements(document_uri, NAO_NUMERICRATING, "?")
		
		return 0

############################################################

	def getNumericRating(self, document_uri):
		""" Get the numeric value for this document_uri.
			:Parameters:
				`document_uri` : str
					The pimo:Document that we want to the get the rating of.
			:Returns: the value registered in the database as the rating for document_uri, or 0 if document_uri has not any rating set.
		"""
		
		self.i.documentChecking(document_uri)
		
		value = self.i.getImage(document_uri, NAO_NUMERICRATING)
		
		if value == None:
			return 0
		else:
			# convert value:string into an integer
			return int(value)

############################################################
	def runQuery(self, query):
		myResult = self.i.executeQueryDisplayableResult(query)
		
		return myResult
